



from django.test import TestCase, Client
from django.urls import reverse
from django.contrib.auth import get_user_model
from django.db import transaction
from django.core.cache import cache
from .models import Book, Author, Publisher, BookLoan
from .signals import book_published
import json

User = get_user_model()

class BookModelTest(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.author = Author.objects.create(
            name='Test Author',
            email='author@test.com',
            birth_date='1980-01-01'
        )
        cls.publisher = Publisher.objects.create(
            name='Test Publisher',
            address='123 Test St',
            website='https://test.com',
            code='TEST123'
        )
        cls.book = Book.objects.create(
            title='Test Book',
            author=cls.author,
            publisher=cls.publisher,
            publish_date='2020-01-01',
            status='published',
            isbn='1234567890123',
            price=29.99,
            pages=300
        )
    
    def test_book_creation(self):
        self.assertEqual(self.book.title, 'Test Book')
        self.assertEqual(self.book.author.name, 'Test Author')
        self.assertEqual(self.book.publisher.name, 'Test Publisher')
    
    def test_book_str(self):
        self.assertEqual(str(self.book), 'Test Book by Test Author')
    
    def test_book_clean(self):
        from django.core.exceptions import ValidationError
        book = Book(
            title='Future Book',
            author=self.author,
            publish_date='2100-01-01',
            status='draft',
            isbn='9876543210987',
            price=19.99,
            pages=200
        )
        with self.assertRaises(ValidationError):
            book.full_clean()
    
    def test_book_cache(self):
        cache_key = f'book_{self.book.id}_details'
        cached_book = cache.get(cache_key)
        self.assertIsNone(cached_book)
        
        # 访问详情视图会设置缓存
        client = Client()
        client.get(reverse('book-detail', args=[self.book.id]))
        
        cached_book = cache.get(cache_key)
        self.assertIsNotNone(cached_book)
        self.assertEqual(cached_book.title, 'Test Book')

class BookViewTest(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.author = Author.objects.create(
            name='View Author',
            email='view@test.com',
            birth_date='1970-01-01'
        )
        cls.books = [
            Book.objects.create(
                title=f'Book {i}',
                author=cls.author,
                publish_date=f'202{i}-01-01',
                status='published',
                isbn=f'123456789012{i}',
                price=10.99 + i,
                pages=200 + i * 10
            ) for i in range(5)
        ]
        cls.user = User.objects.create_user(
            username='testuser',
            password='testpass123'
        )
    
    def test_book_list_view(self):
        response = self.client.get(reverse('book-list'))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Book 4')
        self.assertTemplateUsed(response, 'books/book_list.html')
    
    def test_book_detail_view(self):
        book = self.books[0]
        response = self.client.get(reverse('book-detail', args=[book.id]))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, book.title)
        self.assertTemplateUsed(response, 'books/book_detail.html')
    
    def test_book_create_view_requires_login(self):
        response = self.client.get(reverse('book-create'))
        self.assertEqual(response.status_code, 302)
        self.assertRedirects(response, f'/accounts/login/?next={reverse("book-create")}')
    
    def test_book_create_view_with_login(self):
        self.client.login(username='testuser', password='testpass123')
        response = self.client.get(reverse('book-create'))
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'books/book_form.html')

class BookSignalTest(TestCase):
    def setUp(self):
        self.author = Author.objects.create(
            name='Signal Author',
            email='signal@test.com',
            birth_date='1960-01-01'
        )
    
    def test_book_published_signal(self):
        from django.test import override_settings
        from unittest.mock import patch
        
        with patch('books.signals.logger.info') as mock_logger:
            with override_settings(SIGNALS_ENABLED=True):
                book = Book.objects.create(
                    title='Signal Book',
                    author=self.author,
                    publish_date='2020-01-01',
                    status='published',
                    isbn='1234567890000',
                    price=19.99,
                    pages=250
                )
                
                mock_logger.assert_called_with(f"New book created: {book.title}")
                
                book.status = 'published'
                book.save()
                mock_logger.assert_called_with(f"Book updated: {book.title}")

class BookAPITest(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.author = Author.objects.create(
            name='API Author',
            email='api@test.com',
            birth_date='1950-01-01'
        )
        cls.books = [
            Book.objects.create(
                title=f'API Book {i}',
                author=cls.author,
                publish_date=f'202{i}-01-01',
                status='published',
                isbn=f'12345678901{i}',
                price=15.99 + i,
                pages=150 + i * 20
            ) for i in range(3)
        ]
        cls.user = User.objects.create_user(
            username='apiuser',
            password='testpass123'
        )
        cls.client = Client()
    
    def test_book_api_list(self):
        response = self.client.get('/api/books/')
        self.assertEqual(response.status_code, 200)
        data = json.loads(response.content)
        self.assertEqual(len(data), 3)
        self.assertEqual(data[0]['title'], 'API Book 0')
    
    def test_book_api_detail(self):
        book = self.books[0]
        response = self.client.get(f'/api/books/{book.id}/')
        self.assertEqual(response.status_code, 200)
        data = json.loads(response.content)
        self.assertEqual(data['title'], 'API Book 0')
        self.assertEqual(data['author']['name'], 'API Author')
    
    def test_book_api_create_requires_auth(self):
        response = self.client.post('/api/books/', {
            'title': 'New API Book',
            'author': {'name': 'New Author', 'email': 'new@test.com', 'birth_date': '1990-01-01'},
            'publish_date': '2023-01-01',
            'status': 'published',
            'isbn': '1234567890999',
            'price': 24.99,
            'pages': 350
        }, content_type='application/json')
        self.assertEqual(response.status_code, 403)
    
    def test_book_api_create_with_auth(self):
        self.client.login(username='apiuser', password='testpass123')
        response = self.client.post('/api/books/', {
            'title': 'New API Book',
            'author': {'name': 'New Author', 'email': 'new@test.com', 'birth_date': '1990-01-01'},
            'publish_date': '2023-01-01',
            'status': 'published',
            'isbn': '1234567890999',
            'price': 24.99,
            'pages': 350
        }, content_type='application/json')
        self.assertEqual(response.status_code, 201)
        data = json.loads(response.content)
        self.assertEqual(data['title'], 'New API Book')
        self.assertEqual(data['author']['name'], 'New Author')

class TransactionTest(TestCase):
    def setUp(self):
        self.author = Author.objects.create(
            name='Transaction Author',
            email='transaction@test.com',
            birth_date='1940-01-01'
        )
        self.book = Book.objects.create(
            title='Transaction Book',
            author=self.author,
            publish_date='2020-01-01',
            status='published',
            isbn='1234567890001',
            price=19.99,
            pages=250
        )
        self.user = User.objects.create_user(
            username='transactionuser',
            password='testpass123'
        )
    
    def test_atomic_transaction_success(self):
        initial_price = self.book.price
        
        with transaction.atomic():
            Book.objects.filter(id=self.book.id).update(price=F('price') + 5)
            updated_book = Book.objects.get(id=self.book.id)
            self.assertEqual(updated_book.price, initial_price + 5)
        
        # 事务提交后，更改持久化
        updated_book = Book.objects.get(id=self.book.id)
        self.assertEqual(updated_book.price, initial_price + 5)
    
    def test_atomic_transaction_failure(self):
        initial_price = self.book.price
        
        try:
            with transaction.atomic():
                Book.objects.filter(id=self.book.id).update(price=F('price') + 5)
                raise Exception("Forced error")
        except Exception:
            pass
        
        # 事务回滚后，价格不变
        updated_book = Book.objects.get(id=self.book.id)
        self.assertEqual(updated_book.price, initial_price)